package com.ruoyi.framework.security.jsonlogin;

import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

import java.util.concurrent.TimeUnit;

@Slf4j
@Getter
public class JsonLoginProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;
    private final SysLoginService sysLoginService;
    private final PasswordEncoder passwordEncoder;
    private final UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private final UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    @Setter
    private RedisCache redisCache;
    @Setter
    private boolean verifyPasswordErrors = false;
    @Setter
    private int maxRetryCount = 5;
    @Setter
    private int lockTime = 10;

    public JsonLoginProvider(UserDetailsService userDetailsService, SysLoginService sysLoginService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.sysLoginService = sysLoginService;
        this.passwordEncoder = passwordEncoder;
    }

    public JsonLoginProvider(RedisCache redisCache, UserDetailsService userDetailsService, SysLoginService sysLoginService, PasswordEncoder passwordEncoder) {
        this.redisCache = redisCache;
        this.userDetailsService = userDetailsService;
        this.sysLoginService = sysLoginService;
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * 开始认证
     *
     * @param authentication the authentication request object.
     * @return 认证之后的身份验证信息
     * @throws AuthenticationException 验证异常信息
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(JsonLoginAuthenticationToken.class, authentication, () -> "Only JsonLoginAuthenticationToken is supported");
        // 登录用户信息
        JsonLoginAuthenticationToken jsonLoginAuthenticationToken = (JsonLoginAuthenticationToken) authentication;
        LoginBody loginBody = jsonLoginAuthenticationToken.getLoginBody();
        UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(loginBody.getUsername());
        this.preAuthenticationChecks.check(userDetails);
        additionalAuthenticationChecks(userDetails, jsonLoginAuthenticationToken);
        this.postAuthenticationChecks.check(userDetails);
        return createSuccessAuthentication(authentication, userDetails);
    }


    /**
     * 创建登录成功之后的用户信息
     *
     * @param authentication 凭证 未认证的
     * @param userDetails    用户详情信息
     * @return 成功之后的用户信息
     */
    private Authentication createSuccessAuthentication(Authentication authentication, UserDetails userDetails) {
        JsonLoginAuthenticationToken jsonLoginAuthenticationToken = new JsonLoginAuthenticationToken(userDetails, StringUtils.EMPTY, userDetails.getAuthorities());
        jsonLoginAuthenticationToken.setDetails(authentication.getDetails());
        return jsonLoginAuthenticationToken;
    }

    /**
     * 其他的检查信息
     * 例如检查密码信息
     *
     * @param userDetails                  用户详情
     * @param jsonLoginAuthenticationToken 用户令牌信息 未认证的
     */
    private void additionalAuthenticationChecks(UserDetails userDetails, JsonLoginAuthenticationToken jsonLoginAuthenticationToken) {
        String username = jsonLoginAuthenticationToken.getName();
        String password = (String) jsonLoginAuthenticationToken.getCredentials();
        if (StringUtils.isBlank(password)) {
            throw new BadCredentialsException("请输入用户名密码!");
        }
        if (verifyPasswordErrors) {
            Integer retryCount = redisCache.getCacheObject(sysLoginService.getCacheKey(username));
            if (retryCount == null) {
                retryCount = 0;
            }
            if (retryCount >= maxRetryCount) {
                throw new DisabledException(StrUtil.format("密码输入错误{}次，帐户锁定{}分钟", maxRetryCount, lockTime));
            }
            boolean matches = this.getPasswordEncoder().matches(password, userDetails.getPassword());
            if (!matches) {
                retryCount = retryCount + 1;
                redisCache.setCacheObject(sysLoginService.getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
                throw new BadCredentialsException(StrUtil.format("用户名密码错误,还能输入{}次!", maxRetryCount + 1 - retryCount));
            } else {
                sysLoginService.clearLoginRecordCache(username);
            }
        } else {
            boolean matches = this.getPasswordEncoder().matches(password, userDetails.getPassword());
            if (!matches) {
                throw new BadCredentialsException(StrUtil.format("用户名密码错误!"));
            }
        }
    }


    /**
     * 是否支持该凭证
     *
     * @param authentication 凭证的类
     * @return true 支持
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return JsonLoginAuthenticationToken.class.equals(authentication);
    }

    /**
     * 预检查身份验证
     */
    private static class DefaultPreAuthenticationChecks implements UserDetailsChecker {

        @Override
        public void check(UserDetails userDetails) {

            boolean accountNonLocked = userDetails.isAccountNonLocked();
            if (!accountNonLocked) {
                throw new LockedException("用户帐户已锁定");
            }
            boolean enabled = userDetails.isEnabled();
            if (!enabled) {
                throw new DisabledException("用户已禁用!");
            }
            boolean accountNonExpired = userDetails.isAccountNonExpired();
            if (!accountNonExpired) {
                throw new AccountExpiredException("用户帐户已过期");
            }
        }
    }

    /**
     * 后置检查身份验证
     */
    private static class DefaultPostAuthenticationChecks implements UserDetailsChecker {

        @Override
        public void check(UserDetails userDetails) {
            if (!userDetails.isCredentialsNonExpired()) {
                throw new CredentialsExpiredException("用户凭证已过期!");
            }
        }
    }
}
